<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2010 http://topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think\Model;

use Think\Model;

/**
 * MongoModel模型类
 * 实现了ODM和ActiveRecords模式
 */
class MongoModel extends Model {
	// 主键类型
	const TYPE_OBJECT = 1;
	const TYPE_INT = 2;
	const TYPE_STRING = 3;
	
	// 主键名称
	protected $pk = '_id';
	// _id 类型 1 Object 采用MongoId对象 2 Int 整形 支持自动增长 3 String 字符串Hash
	protected $_idType = self::TYPE_OBJECT;
	// 主键是否自增
	protected $_autoinc = true;
	// Mongo默认关闭字段检测 可以动态追加字段
	protected $autoCheckFields = false;
	// 链操作方法列表
	protected $methods = array (
			'table',
			'order',
			'auto',
			'filter',
			'validate' 
	);
	
	/**
	 * 利用__call方法实现一些特殊的Model方法
	 * 
	 * @access public
	 * @param string $method
	 *        	方法名称
	 * @param array $args
	 *        	调用参数
	 * @return mixed
	 */
	public function __call($method, $args) {
		if (in_array ( strtolower ( $method ), $this->methods, true )) {
			// 连贯操作的实现
			$this->options [strtolower ( $method )] = $args [0];
			return $this;
		} elseif (strtolower ( substr ( $method, 0, 5 ) ) == 'getby') {
			// 根据某个字段获取记录
			$field = parse_name ( substr ( $method, 5 ) );
			$where [$field] = $args [0];
			return $this->where ( $where )->find ();
		} elseif (strtolower ( substr ( $method, 0, 10 ) ) == 'getfieldby') {
			// 根据某个字段获取记录的某个值
			$name = parse_name ( substr ( $method, 10 ) );
			$where [$name] = $args [0];
			return $this->where ( $where )->getField ( $args [1] );
		} else {
			E ( __CLASS__ . ':' . $method . L ( '_METHOD_NOT_EXIST_' ) );
			return;
		}
	}
	
	/**
	 * 获取字段信息并缓存 主键和自增信息直接配置
	 * 
	 * @access public
	 * @return void
	 */
	public function flush() {
		// 缓存不存在则查询数据表信息
		$fields = $this->db->getFields ();
		if (! $fields) { // 暂时没有数据无法获取字段信息 下次查询
			return false;
		}
		$this->fields = array_keys ( $fields );
		foreach ( $fields as $key => $val ) {
			// 记录字段类型
			$type [$key] = $val ['type'];
		}
		// 记录字段类型信息
		if (C ( 'DB_FIELDTYPE_CHECK' ))
			$this->fields ['_type'] = $type;
			
			// 2008-3-7 增加缓存开关控制
		if (C ( 'DB_FIELDS_CACHE' )) {
			// 永久缓存数据表信息
			$db = $this->dbName ? $this->dbName : C ( 'DB_NAME' );
			F ( '_fields/' . $db . '.' . $this->name, $this->fields );
		}
	}
	
	// 写入数据前的回调方法 包括新增和更新
	protected function _before_write(&$data) {
		$pk = $this->getPk ();
		// 根据主键类型处理主键数据
		if (isset ( $data [$pk] ) && $this->_idType == self::TYPE_OBJECT) {
			$data [$pk] = new \MongoId ( $data [$pk] );
		}
	}
	
	/**
	 * count统计 配合where连贯操作
	 * 
	 * @access public
	 * @return integer
	 */
	public function count() {
		// 分析表达式
		$options = $this->_parseOptions ();
		return $this->db->count ( $options );
	}
	
	/**
	 * 获取唯一值
	 * 
	 * @access public
	 * @return array | false
	 */
	public function distinct($field, $where = array()) {
		// 分析表达式
		$this->options = $this->_parseOptions ();
		$this->options ['where'] = array_merge ( ( array ) $this->options ['where'], $where );
		
		$command = array (
				"distinct" => $this->options ['table'],
				"key" => $field,
				"query" => $this->options ['where'] 
		);
		
		$result = $this->command ( $command );
		return isset ( $result ['values'] ) ? $result ['values'] : false;
	}
	
	/**
	 * 获取下一ID 用于自动增长型
	 * 
	 * @access public
	 * @param string $pk
	 *        	字段名 默认为主键
	 * @return mixed
	 */
	public function getMongoNextId($pk = '') {
		if (empty ( $pk )) {
			$pk = $this->getPk ();
		}
		return $this->db->getMongoNextId ( $pk );
	}
	
	/**
	 * 新增数据
	 * 
	 * @access public
	 * @param mixed $data
	 *        	数据
	 * @param array $options
	 *        	表达式
	 * @param boolean $replace
	 *        	是否replace
	 * @return mixed
	 */
	public function add($data = '', $options = array(), $replace = false) {
		if (empty ( $data )) {
			// 没有传递数据，获取当前数据对象的值
			if (! empty ( $this->data )) {
				$data = $this->data;
				// 重置数据
				$this->data = array ();
			} else {
				$this->error = L ( '_DATA_TYPE_INVALID_' );
				return false;
			}
		}
		// 分析表达式
		$options = $this->_parseOptions ( $options );
		// 数据处理
		$data = $this->_facade ( $data );
		if (false === $this->_before_insert ( $data, $options )) {
			return false;
		}
		// 写入数据到数据库
		$result = $this->db->insert ( $data, $options, $replace );
		if (false !== $result) {
			$this->_after_insert ( $data, $options );
			if (isset ( $data [$this->getPk ()] )) {
				return $data [$this->getPk ()];
			}
		}
		return $result;
	}
	
	// 插入数据前的回调方法
	protected function _before_insert(&$data, $options) {
		// 写入数据到数据库
		if ($this->_autoinc && $this->_idType == self::TYPE_INT) { // 主键自动增长
			$pk = $this->getPk ();
			if (! isset ( $data [$pk] )) {
				$data [$pk] = $this->db->getMongoNextId ( $pk );
			}
		}
	}
	public function clear() {
		return $this->db->clear ();
	}
	
	// 查询成功后的回调方法
	protected function _after_select(&$resultSet, $options) {
		array_walk ( $resultSet, array (
				$this,
				'checkMongoId' 
		) );
	}
	
	/**
	 * 获取MongoId
	 * 
	 * @access protected
	 * @param array $result
	 *        	返回数据
	 * @return array
	 */
	protected function checkMongoId(&$result) {
		if (is_object ( $result ['_id'] )) {
			$result ['_id'] = $result ['_id']->__toString ();
		}
		return $result;
	}
	
	// 表达式过滤回调方法
	protected function _options_filter(&$options) {
		$id = $this->getPk ();
		if (isset ( $options ['where'] [$id] ) && is_scalar ( $options ['where'] [$id] ) && $this->_idType == self::TYPE_OBJECT) {
			$options ['where'] [$id] = new \MongoId ( $options ['where'] [$id] );
		}
	}
	
	/**
	 * 查询数据
	 * 
	 * @access public
	 * @param mixed $options
	 *        	表达式参数
	 * @return mixed
	 */
	public function find($options = array()) {
		if (is_numeric ( $options ) || is_string ( $options )) {
			$id = $this->getPk ();
			$where [$id] = $options;
			$options = array ();
			$options ['where'] = $where;
		}
		// 分析表达式
		$options = $this->_parseOptions ( $options );
		$result = $this->db->find ( $options );
		if (false === $result) {
			return false;
		}
		if (empty ( $result )) { // 查询结果为空
			return null;
		} else {
			$this->checkMongoId ( $result );
		}
		$this->data = $result;
		$this->_after_find ( $this->data, $options );
		return $this->data;
	}
	
	/**
	 * 字段值增长
	 * 
	 * @access public
	 * @param string $field
	 *        	字段名
	 * @param integer $step
	 *        	增长值
	 * @return boolean
	 */
	public function setInc($field, $step = 1) {
		return $this->setField ( $field, array (
				'inc',
				$step 
		) );
	}
	
	/**
	 * 字段值减少
	 * 
	 * @access public
	 * @param string $field
	 *        	字段名
	 * @param integer $step
	 *        	减少值
	 * @return boolean
	 */
	public function setDec($field, $step = 1) {
		return $this->setField ( $field, array (
				'inc',
				'-' . $step 
		) );
	}
	
	/**
	 * 获取一条记录的某个字段值
	 * 
	 * @access public
	 * @param string $field
	 *        	字段名
	 * @param string $spea
	 *        	字段数据间隔符号
	 * @return mixed
	 */
	public function getField($field, $sepa = null) {
		$options ['field'] = $field;
		$options = $this->_parseOptions ( $options );
		if (strpos ( $field, ',' )) { // 多字段
			if (is_numeric ( $sepa )) { // 限定数量
				$options ['limit'] = $sepa;
				$sepa = null; // 重置为null 返回数组
			}
			$resultSet = $this->db->select ( $options );
			if (! empty ( $resultSet )) {
				$_field = explode ( ',', $field );
				$field = array_keys ( $resultSet [0] );
				$key = array_shift ( $field );
				$key2 = array_shift ( $field );
				$cols = array ();
				$count = count ( $_field );
				foreach ( $resultSet as $result ) {
					$name = $result [$key];
					if (2 == $count) {
						$cols [$name] = $result [$key2];
					} else {
						$cols [$name] = is_null ( $sepa ) ? $result : implode ( $sepa, $result );
					}
				}
				return $cols;
			}
		} else {
			// 返回数据个数
			if (true !== $sepa) { // 当sepa指定为true的时候 返回所有数据
				$options ['limit'] = is_numeric ( $sepa ) ? $sepa : 1;
			} // 查找符合的记录
			$result = $this->db->select ( $options );
			if (! empty ( $result )) {
				if (1 == $options ['limit'])
					return reset ( $result )[$field];
				foreach ( $result as $val ) {
					$array [] = $val [$field];
				}
				return $array;
			}
		}
		return null;
	}
	
	/**
	 * 执行Mongo指令
	 * 
	 * @access public
	 * @param array $command
	 *        	指令
	 * @return mixed
	 */
	public function command($command, $options = array()) {
		$options = $this->_parseOptions ( $options );
		return $this->db->command ( $command, $options );
	}
	
	/**
	 * 执行MongoCode
	 * 
	 * @access public
	 * @param string $code
	 *        	MongoCode
	 * @param array $args
	 *        	参数
	 * @return mixed
	 */
	public function mongoCode($code, $args = array()) {
		return $this->db->execute ( $code, $args );
	}
	
	// 数据库切换后回调方法
	protected function _after_db() {
		// 切换Collection
		$this->db->switchCollection ( $this->getTableName (), $this->dbName ? $this->dbName : C ( 'db_name' ) );
	}
	
	/**
	 * 得到完整的数据表名 Mongo表名不带dbName
	 * 
	 * @access public
	 * @return string
	 */
	public function getTableName() {
		if (empty ( $this->trueTableName )) {
			$tableName = ! empty ( $this->tablePrefix ) ? $this->tablePrefix : '';
			if (! empty ( $this->tableName )) {
				$tableName .= $this->tableName;
			} else {
				$tableName .= parse_name ( $this->name );
			}
			$this->trueTableName = strtolower ( $tableName );
		}
		return $this->trueTableName;
	}
	
	/**
	 * 分组查询
	 * 
	 * @access public
	 * @return string
	 */
	public function group($key, $init, $reduce, $option = array()) {
		$option = $this->_parseOptions ( $option );
		
		// 合并查询条件
		if (isset ( $option ['where'] ))
			$option ['condition'] = array_merge ( ( array ) $option ['condition'], $option ['where'] );
		
		return $this->db->group ( $key, $init, $reduce, $option );
	}
	
	/**
	 * 返回Mongo运行错误信息
	 * 
	 * @access public
	 * @return json
	 */
	public function getLastError() {
		return $this->db->command ( array (
				'getLastError' => 1 
		) );
	}
	
	/**
	 * 返回指定集合的统计信息，包括数据大小、已分配的存储空间和索引的大小
	 * 
	 * @access public
	 * @return json
	 */
	public function status() {
		$option = $this->_parseOptions ();
		return $this->db->command ( array (
				'collStats' => $option ['table'] 
		) );
	}
	
	/**
	 * 取得当前数据库的对象
	 * 
	 * @access public
	 * @return object
	 */
	public function getDB() {
		return $this->db->getDB ();
	}
	
	/**
	 * 取得集合对象，可以进行创建索引等查询
	 * 
	 * @access public
	 * @return object
	 */
	public function getCollection() {
		return $this->db->getCollection ();
	}
}